﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using Microsoft.Maps.MapControl;
using System.Xml.Serialization;
using System.IO;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml;
using System.Collections.Specialized;
using System.Xml.Linq;
using SilverlightMapCreator.Mathemathics;
namespace SilverlightMapCreator
{
    public partial class MainPage : UserControl, INotifyPropertyChanged
    {
        private bool _dragging;
        private Point? _oldPosition;
        private double _imageZoom;

        #region private fields
        private CorrespondenceSystem _corespondanceSystem = new CorrespondenceSystem();
        private WriteableBitmap _wBitmap;
        private BitmapImage _imageSource;

        #endregion

        #region public properties
        ObservableCollection<Corespondence> _corespondances;

        private ObservableCollection<Color> selectedColors;
        public ObservableCollection<Color> SelectedColors
        {
            get
            {
                if (selectedColors == null)
                {
                    selectedColors = new ObservableCollection<Color>();
                }
                return selectedColors;
            }
            set
            {
                selectedColors = value;
                OnPropertyChanged("ObservableCollection");
            }
        }

        private Dictionary<Color, List<List<MyPoint>>> _pointsDictionary;

        private List<List<List<Location>>> _resultSet;

        public List<List<List<Location>>> ResultSet
        {
            get { return _resultSet; }
            set {
                _resultSet = value;
                OnPropertyChanged("ResultSet");
            }
        }


        public ObservableCollection<Corespondence> Corespondances
        {
            get {
                if (_corespondances == null)
                {
                    _corespondances = new ObservableCollection<Corespondence>();
                }
                return _corespondances; }
            set { 
                _corespondances = value;
                OnPropertyChanged("Corespondances");
            }
        }
        private Corespondence _selectedCorespondance;
        public Corespondence SelectedCorespondance
        {
            get { return _selectedCorespondance; }
            set { 
                _selectedCorespondance = value;
                OnPropertyChanged("SelectedCorespondance");
            }
        }

        private Mode _mode;
        public Mode Mode
        {
            get { return _mode; }
            set { 
                _mode = value;
                OnPropertyChanged("Mode");
            }
        }

        #endregion

        #region constructor

        public MainPage()
        {
            InitializeComponent();
            ColorParamSlider.Value = 5;

            DistanceParamSlider.Value = 5;
            //connect the routes which probably should be connected in distance of 100m
            DistanceToConnectSlider.Value = 100;
            MinPointsPerRouteSlider.Value = 3;
            this.DataContext = this;
            Corespondances.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Corespondances_CollectionChanged);
        }

        #endregion

        public int SearchMyRoute(List<List<MyPoint>> list, MyPoint point, int currentIndex, out double minDist)
        {
            int distanceParam = (int)DistanceParamSlider.Value;
            minDist = Int16.MaxValue;
            int index = -1;
            for(int i=0;i<list.Count();i++)
            {
                var route = list[i];
                if (route.Count > 0)
                {
                    var last = route.Last();
                    var distance = GeoMath.Distance(point, last);

                    if (distance < distanceParam && distance < minDist)
                    {
                        minDist = distance;
                        index = i;
                    }
                }
            }
            return index;
        }

        public Dictionary<Color,List<List<MyPoint>>> AnalyseMap(WriteableBitmap wBitmap,IEnumerable<Color> colors, int distanceParam, 
            double minDistance, int colorParam)
        {
            if (wBitmap == null)
            {
                MessageBox.Show("You have to open a picture and set up correpondances before starting");
                return null;
            }

            Dictionary<Color, List<List<MyPoint>>> pointsDictionary = new Dictionary<Color, List<List<MyPoint>>>();

            _corespondanceSystem.ComputeParameters(Corespondances.ToList());

            map.Children.Clear();

            foreach (Color c in SelectedColors)
            {
                if (!pointsDictionary.ContainsKey(c))
                {
                    pointsDictionary.Add(c, new List<List<MyPoint>>());
                    pointsDictionary[c].Add(new List<MyPoint>());
                }
            }

            for (int row = 0; row < wBitmap.PixelHeight; row++)
            {
                int currentRouteIndex = 0;
                int column = 0;

                while (column < wBitmap.PixelWidth)
                {
                    int toAdd = 0;


                    //get pixel and its color
                    int pixel = wBitmap.Pixels[row * wBitmap.PixelWidth + column];

                    Color color = Color.FromArgb(255, (byte)((pixel >> 16) & 0xFF), (byte)((pixel >> 8) & 0xFF), (byte)(pixel & 0xFF));

                    ////get 3 pixels to right
                    if (column < wBitmap.PixelWidth - 2)
                    {
                        int other = wBitmap.Pixels[row * wBitmap.PixelWidth + column + 2];
                        Color otherColor = Color.FromArgb(255, (byte)((other >> 16) & 0xFF), (byte)((other >> 8) & 0xFF), (byte)(other & 0xFF));

                        // +++ if there are 3 pixels of same color
                        if (Math.Abs(color.R - otherColor.R) < colorParam && Math.Abs(color.G - otherColor.G) < colorParam && Math.Abs(color.B - otherColor.B) < colorParam)
                        {

                            toAdd = 1;
                            pixel = wBitmap.Pixels[row * wBitmap.PixelHeight + column + toAdd];
                            color = Color.FromArgb(255, (byte)((pixel >> 16) & 0xFF), (byte)((pixel >> 8) & 0xFF), (byte)(pixel & 0xFF));
                        }
                    }

                    column += toAdd;
                    foreach (Color selectedColor in SelectedColors)
                    {
                        if (Math.Abs(color.R - selectedColor.R) < colorParam && Math.Abs(color.G - selectedColor.G) < colorParam && Math.Abs(color.B - selectedColor.B) < colorParam)
                        {
                            //create new point
                            MyPoint point = new MyPoint(column, wBitmap.PixelHeight - row);

                            double distance;
                            int routeIndex = SearchMyRoute(pointsDictionary[selectedColor], point, currentRouteIndex, out distance);
                            if (routeIndex != -1)
                            {
                                currentRouteIndex = routeIndex;
                            }
                            else
                            {
                                pointsDictionary[selectedColor].Add(new List<MyPoint>());

                                currentRouteIndex = pointsDictionary[selectedColor].Count - 1;
                            }

                            if (distance > minDistance)
                            {
                                int count = pointsDictionary[selectedColor][currentRouteIndex].Count();
                                pointsDictionary[selectedColor][currentRouteIndex].Add(point);

                                if (count > 3)
                                {
                                    MyPoint a = pointsDictionary[selectedColor][currentRouteIndex][count - 3];
                                    MyPoint b = pointsDictionary[selectedColor][currentRouteIndex][count - 2];
                                    if (Graphics.Colinear(a, b, point))
                                    {
                                        pointsDictionary[selectedColor][currentRouteIndex].Remove(b);
                                    }
                                }

                            }
                        }
                    }
                    column += toAdd + 1;
                }
            }
            return pointsDictionary;
        }

        public List<List<List<Location>>> CleanAndConvert(List<List<List<MyPoint>>> pointsDictionary, int minPointsPerRoute)
        {

            List<List<List<Location>>> locationsDictionary = new List<List<List<Location>>>();

            foreach (var bikeRoute in pointsDictionary)
            {
                List<List<Location>> locationCollections = new List<List<Location>>();
                foreach (var route in bikeRoute)
                {
                    if (route.Count > minPointsPerRoute)
                    {
                        var cleaned = Graphics.RemovePointsOnSameLine(route);
                        var locationCollection = cleaned.Select(x => _corespondanceSystem.PointToLocationAffinity(x.X, x.Y)).ToList();
                        locationCollections.Add(locationCollection);
                    }
                }
                //if bikeRoute is composed at least of one route
                if (locationCollections.Count >= 1)
                {
                    locationsDictionary.Add(locationCollections);
                }
            }

            locationsDictionary = MapAnalytics.ConcatenateDisconectedRoutes(locationsDictionary,(int)DistanceToConnectSlider.Value);
            locationsDictionary = MapAnalytics.ConnectpointsDictionary(locationsDictionary, (int)DistanceToConnectSlider.Value);
            return locationsDictionary;
        }
  
        public void ShowRoutes(List<List<List<Location>>> routeList)
        {
            foreach (var bikeRoute in routeList)
            {
                foreach (var route in bikeRoute)
                {
                    ShowRoute(route, new SolidColorBrush(Colors.Red));
                }
            }
        }

        public void ShowRoute(List<Location> locList,SolidColorBrush brush)
        {
            
            LocationCollection collection = new LocationCollection();

            foreach (Location loc in locList)
            {
                
                collection.Add(loc);
                DraggablePushpin pushpin = new DraggablePushpin();
                pushpin.Route = locList;
                pushpin.Collection = collection;
                pushpin.Location = loc;
                pushpin.MouseRightButtonDown += new MouseButtonEventHandler(pushpin_MouseRightButtonDown);

                map.Children.Add(pushpin);
            }

            MapPolyline line = new MapPolyline();
            line.StrokeThickness = 2;
            line.Stroke = new SolidColorBrush(Colors.Black);// brush;
            line.Locations = collection;
            map.Children.Add(line);
            map.UpdateLayout();
            
        }


        #region Button Handlers
        private Stream GetStreamUsingSaveDialog()
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.DefaultExt = ".txt";

            bool? dialogResult = dialog.ShowDialog();

            if (dialogResult == true)
            {
                return dialog.OpenFile();
            }
            return null;
        }

        private Stream GetStreamUsingOpenDialog()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Multiselect = false;
            bool? result = ofd.ShowDialog();
            if (!result.HasValue || result.Value == false)
                return null;
            return ofd.File.OpenRead();
        }

        private void ExportOsm_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                using (GetStreamUsingSaveDialog())
                {
                    OsmProcessing.CreateOsmFile(ResultSet);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error while saving the data " + ex.Message);
            }
        }

        private void ExportBikeRoutes_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                using (GetStreamUsingSaveDialog())
                {
                    OsmProcessing.ExportToBikeFormat(ResultSet);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error while exporting data " + ex.Message);
            }
        }

        //TODO: this methods kick out.
        private void OpenBikeRoutes_Click(object sender, RoutedEventArgs e)
        {
            
            try
            {
                using (Stream stream = GetStreamUsingOpenDialog())
                {
                    //XmlReader reader = XmlReader.Create(stream);
                    XDocument xDoc = XDocument.Load(stream);
                    var routes = from route in xDoc.Descendants("ways").Descendants("way")
                                 select new
                                 {
                                     Nodes = (from node in route.Descendants("nd")
                                              select new Location
                                              {
                                                  Longitude = Convert.ToDouble(node.Attribute("lng").Value),
                                                  Latitude = Convert.ToDouble(node.Attribute("lat").Value)
                                              }).ToList()
                                 };

                    foreach (var route in routes)
                    {
                        if (route.Nodes.Count > 2)
                        {
                            LocationCollection col = new LocationCollection();
                            foreach (var loc in route.Nodes)
                            {
                                col.Add(loc);
                            }

                            MapPolyline line = new MapPolyline();
                            line.StrokeThickness = 2;
                            line.Stroke = new SolidColorBrush(Colors.Black);// brush;
                            line.Locations = col;
                            map.Children.Add(line);
                        }
                    }
                    map.UpdateLayout();


                    
                }

            }
            catch (Exception ex)
            {
                Message.Text = "Error Loading File:" + ex.Message;
            }
        }

        /// <summary>
        /// Click handler for saving created corespondances between image and maps position
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveCorespondances_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                using (Stream stream = GetStreamUsingSaveDialog())
                {
                    XmlWriter writer = XmlWriter.Create(stream);
                    XmlSerializer serializer = new XmlSerializer(typeof(Situation));
                    serializer.Serialize(stream, new Situation { Correspondances = Corespondances.ToList() });
                }
            }
            catch (Exception ex)
            {
                Message.Text = "Error Saving File: " + ex.Message;
            }
        }

        /// <summary>
        /// Button handler which starts the analysis of the map and image
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void StartAnalysis_Click(object sender, RoutedEventArgs e)
        {
            int distanceParam = (int)DistanceParamSlider.Value;
            int colorParam = (int)ColorParamSlider.Value;
            double minDistance = 2.3;
            _pointsDictionary = AnalyseMap(_wBitmap, SelectedColors, distanceParam, minDistance, colorParam);

            var list = _pointsDictionary.Select(x => x.Value).ToList();

            var routes = CleanAndConvert(list, (int)MinPointsPerRouteSlider.Value);
            ShowRoutes(routes);
        }

        /// <summary>
        /// Open previously saved set of corespondances
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenCorespondances_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                using (Stream stream = GetStreamUsingOpenDialog())
                {

                    XmlReader reader = XmlReader.Create(stream);
                    XmlSerializer serializer = new XmlSerializer(typeof(Situation));
                    var situation = serializer.Deserialize(reader) as Situation;
                    Corespondances = new ObservableCollection<Corespondence>(situation.Correspondances);
                    _corespondanceSystem.ComputeParameters(situation.Correspondances);
                }

            }
            catch (Exception ex)
            {
                Message.Text = "Error Loading File: " + ex.Message;
            }
        }

        /// <summary>
        /// Click handler - to enable opening and image
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BrowseClick(object sender, RoutedEventArgs e)
        {
           
            _imageSource = new BitmapImage();
            try
            {
                _imageSource.SetSource(GetStreamUsingOpenDialog());
                ImageHolder.Source = _imageSource;
                _wBitmap = new WriteableBitmap(_imageSource);
            }
            catch (Exception)
            {
                Message.Text = "Error Loading File";
            }
        }
        #endregion

        #region Mouse Events
        private void map_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (Mode == Mode.Corespondaces)
            {
                var mouseMapPosition = e.GetPosition(map);
                var mouseGeocode = map.ViewportPointToLocation(mouseMapPosition);
                if (SelectedCorespondance != null)
                {
                    SelectedCorespondance.Location = mouseGeocode;
                }
            }
        }

        private void ImageHolder_MouseWheel(object sender, MouseWheelEventArgs e)
        {

            var xDif = ImageScrollViewer.VerticalOffset;
            var yDif = ImageScrollViewer.HorizontalOffset;

            var center = e.GetPosition(ImageHolder);
            center.X += xDif;
            center.Y += yDif;
            

            ImageScalTransform.CenterX = (int)Math.Round(center.X);
            ImageScalTransform.CenterY = (int)Math.Round(center.Y);

            ImageScalTransform.ScaleX += e.Delta / 90;
            ImageScalTransform.ScaleY += e.Delta / 90;

            _imageZoom = ImageScalTransform.ScaleX;

            if (ImageScalTransform.ScaleX < 1 || ImageScalTransform.ScaleY < 1)
            {
                _imageZoom = 1;
                ImageScalTransform.ScaleX = 1;
                ImageScalTransform.ScaleY = 1;
            }
            ImageLayoutTransformer.ApplyLayoutTransform();
            e.Handled = true;
        }

        void pushpin_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            DraggablePushpin pushpin = sender as DraggablePushpin;
            pushpin.Route.Remove(pushpin.Location);
            pushpin.Collection.Remove(pushpin.Location);
            map.Children.Remove(pushpin);
            map.UpdateLayout();

        }

        private void ImageHolder_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {

            System.Windows.Controls.Image image = sender as System.Windows.Controls.Image;


            double widthration = _wBitmap.PixelWidth / image.ActualWidth;
            double heightratio = _wBitmap.PixelHeight / image.ActualHeight;

            double x = e.GetPosition(image).X * widthration;
            double y = e.GetPosition(image).Y * heightratio;

            if (Mode == Mode.ColorsSelection)
            {

                int pixel = _wBitmap.Pixels[_wBitmap.PixelWidth * (int)y + (int)x];
                Color color = Color.FromArgb(255, (byte)((pixel >> 16) & 0xFF), (byte)((pixel >> 8) & 0xFF), (byte)(pixel & 0xFF));
                SelectedColors.Add(color);
                OnPropertyChanged("SelectedColors");
            }
            else if (Mode == Mode.Corespondaces)
            {
                MyPoint point = new MyPoint(x, _wBitmap.PixelHeight - y);
                Corespondances.Add(new Corespondence() { Point = point });
            }

            e.Handled = true;
        }
        #endregion

        /// <summary>
        /// Ensuring that when user create corespondance, it is automatically activated to be updated
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Corespondances_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems.Count >= 1)
            {
                SelectedCorespondance = e.NewItems[0] as Corespondence;
            }
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(String property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

        #endregion

        private void ImageHolder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _dragging = true;
            _oldPosition = e.GetPosition(ImageHolder);
        }

        private void ImageHolder_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _dragging = false;
            _oldPosition = null;
        }

        private void ImageHolder_MouseMove(object sender, MouseEventArgs e)
        {
            if (_dragging)
            {
                var position = e.GetPosition(ImageHolder);
                //if (_oldPosition == null)
                //{
                //    _oldPosition = position;
                //    return;
                //}


                var deltaX = _imageZoom * (position.X - _oldPosition.Value.X);
                var deltaY = _imageZoom * (position.Y - _oldPosition.Value.Y);

                //if (Math.Abs(deltaX)> 1 && Math.Abs(deltaY) > 1)
                //{
                    _oldPosition = position;
                    ImageScrollViewer.ScrollToHorizontalOffset(ImageScrollViewer.HorizontalOffset - deltaX);
                    ImageScrollViewer.ScrollToVerticalOffset(ImageScrollViewer.VerticalOffset - deltaY);
                    
                //}               
            }
        }

    }
}
